/*
 *	OpenSource code or fragments by avocado systems, Marcus Fuhrmeister
 *
 *	Redistribution and use in source and binary forms, with or without
 *	modification, are permitted provided that the following conditions
 *	are met:
 *  
 *		* Redistribution of source code must retain the above OpenSource comment,
 *		this list of conditions and the following disclaimer.
 * 
 *		* Redistribution in binary form must reproduce the above OpenSource comment,
 *		this list of conditions and the following disclaimer in the
 *		documentation and/or other materials provided with the distribution.
 * 
 *	Neither the name of 'avocaod systems, Marcus Fuhrmeister' or the
 *	names of contributors may be used to endorse or promote products derived
 *	from this software without specific prior written permission.
 *
 *   This program is free software; you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation; either version 2 of the License, or
 *   (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program; if not, write to the Free Software
 *	Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 
 */

package de.fuhrmeister.browserchooser.controller;

import java.awt.Component;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.awt.event.MouseEvent;
import java.io.File;
import java.util.Iterator;
import java.util.Map.Entry;

import javax.swing.JButton;
import javax.swing.JTextField;

import de.fuhrmeister.browserchooser.model.BrowserModel;
import de.fuhrmeister.browserchooser.model.BrowserVersionModel;
import de.fuhrmeister.browserchooser.model.ModelFactory;
import de.fuhrmeister.browserchooser.view.BrowserVersionView;
import de.fuhrmeister.browserchooser.view.BrowserView;
import de.fuhrmeister.util.ChangeList;
import de.fuhrmeister.util.ConfigManager;
import de.fuhrmeister.util.FileChooser;
import de.fuhrmeister.util.FileManager;
import de.fuhrmeister.util.OSValidator;
import de.fuhrmeister.util.Tools;

/**
 * Controller class handling the views.
 *  
 *	@date		24.08.2010
 *	@author		Marcus Fuhrmeister
 */
public class BrowserController implements ActionListener, FocusListener {

	private static BrowserController self;
	private BrowserView[] browserViews;
	private final ChangeList changeList = new ChangeList();

	/**
	 * Singleton pattern.
	 * 
	 * @return this
	 */
	public static synchronized BrowserController getInstance() {
		if (self == null) {
			BrowserController.self = new BrowserController();
			return BrowserController.self;
		}
		return BrowserController.self;
	}

	/**
	 * Inits the <code>BrowserView</code>s if the model exits and registers the <code>BrowserVersionController</code>
	 * with the <code>BrowserVersionView</code>s.
	 */
	private void init() {
		if (ModelFactory.modelExists()) {
			browserViews = new BrowserView[ModelFactory.getBrowserModelCount()];
			final Iterator<BrowserModel> ibm = ModelFactory.getBrowserModels();
			while (ibm.hasNext()) {
				final BrowserModel bm = ibm.next();
				final BrowserView newBrowserView = new BrowserView(Integer.parseInt(Tools.configMng.getConfiguration(ConfigManager.SETTINGS_SIZE_WIDTH)));
				if (bm.getName().startsWith(Tools.configMng.getStringFor(ConfigManager.STRING_LABEL_NEWBROWSER))) {
					bm.setName(Tools.configMng.getStringFor(ConfigManager.STRING_LABEL_NEWBROWSER) + " - " + bm.getID());
				}
				newBrowserView.setBrowserNameProperties(Tools.configMng.getStringFor(ConfigManager.STRING_LABEL_BROWSERNAME), bm.getName());
				newBrowserView.setBrowserIconProperties(Tools.configMng.getStringFor(ConfigManager.STRING_LABEL_BROWSERICON), (bm.getIcon() != null && bm.getIcon() != "") ? bm.getIcon() : null, Tools.imageMng.createIcon((bm.getIcon() != null && bm.getIcon() != "") ? bm.getIcon() : null, Integer.parseInt(Tools.configMng.getConfiguration(ConfigManager.DOCKLET_ICON_SIZE_MIN)), (bm.get(0).getExec() != null && bm.get(0).getExec() != "") ? bm.get(0).getExec() : null), Tools.configMng.getStringFor(ConfigManager.STRING_BUTTON_BROWSE));
				newBrowserView.setBrowserNewProperties(Tools.configMng.getStringFor(ConfigManager.STRING_BUTTON_BROWSERNEW));
				newBrowserView.setBrowserDelete(Tools.configMng.getStringFor(ConfigManager.STRING_BUTTON_DELETE));
				newBrowserView.setBrowserID(bm.getID());
				final Iterator<BrowserVersionModel> ibvm = bm.iterator();
				boolean isFirstView = true;
				while (ibvm.hasNext()) {
					final BrowserVersionView bvv = BrowserVersionController.getInstance().getView(bm.getID(), ibvm.next());
					bvv.registerListener(this);
					if (isFirstView) {
						bvv.enableBrowserDelete(false);
					}
					newBrowserView.addVersionView(bvv);
					isFirstView = false;
				}
				newBrowserView.registerListener(this);
				browserViews[newBrowserView.getBrowserID() - 1] = newBrowserView;
			}
		}
	}

	/**
	 * Calls its corresponding method with null arguments.
	 * 
	 * @return the new <code>BrowserView</code>
	 */
	public BrowserView createView() {
		return this.createView(null, null);
	}

	/**
	 * Creates a new <code>BrowserView</code> with the given arguments and registers the <code>BrowserVersionController</code>
	 * with the <code>BrowserVersionView</code>s.
	 * 
	 * @param name the name of the browser
	 * @param absolutePath the path to the executable
	 * @return the new <code>BrowserView</code>
	 */
	public BrowserView createView(String name, String absolutePath) {
		final BrowserView newBrowserView = new BrowserView(Integer.parseInt(Tools.configMng.getConfiguration(ConfigManager.SETTINGS_SIZE_WIDTH)));
		name = (name != null) ? name : Tools.configMng.getStringFor(ConfigManager.STRING_LABEL_NEWBROWSER) + " - " + ((browserViews != null) ? browserViews.length + 1 : 1); 
		newBrowserView.setBrowserNameProperties(Tools.configMng.getStringFor(ConfigManager.STRING_LABEL_BROWSERNAME), name);
		newBrowserView.setBrowserIconProperties(Tools.configMng.getStringFor(ConfigManager.STRING_LABEL_BROWSERICON), null, Tools.imageMng.createIcon( null, Integer.parseInt(Tools.configMng.getConfiguration(ConfigManager.DOCKLET_ICON_SIZE_MIN)), absolutePath), Tools.configMng.getStringFor(ConfigManager.STRING_BUTTON_BROWSE));
		newBrowserView.setBrowserNewProperties(Tools.configMng.getStringFor(ConfigManager.STRING_BUTTON_BROWSERNEW));
		newBrowserView.setBrowserDelete(Tools.configMng.getStringFor(ConfigManager.STRING_BUTTON_DELETE));
		newBrowserView.setBrowserID(((browserViews != null) ? browserViews.length + 1 : 1));
		final BrowserVersionView bvv = BrowserVersionController.getInstance().createView(((browserViews != null) ? browserViews.length + 1 : 1), absolutePath).enableBrowserDelete(false);
		bvv.registerListener(this);
		newBrowserView.addVersionView(bvv);
		newBrowserView.registerListener(this);
		final BrowserView[] dummy = new BrowserView[((browserViews != null) ? browserViews.length + 1 : 1)];
		if (browserViews != null && browserViews.length != 0 )System.arraycopy(browserViews, 0, dummy, 0, browserViews.length);
		browserViews = dummy;
		browserViews[newBrowserView.getBrowserID() - 1] = newBrowserView;
		this.addToChangeList(new Integer(newBrowserView.getBrowserID()), ChangeList.SAVE);
		return newBrowserView;
	}

	/**
	 * Adds a new <code>BrowserVersionView</code> to the <code>BrowserView</code> with the given id.
	 * Creates a new change list element to "remember" the model needs to be updated.
	 * 
	 * @param browserID the browser id
	 */
	private void addBrowserVersionView(final int browserID) {
		browserViews[browserID - 1].addVersionView(BrowserVersionController.getInstance().createView(browserID));
		this.addToChangeList(new Integer(browserID), ChangeList.SAVE);
	}
	
	/**
	 * Adds a new <code>BrowserVersionView</code> and its path to the executable
	 * to the <code>BrowserView</code> with the given id.
	 * Creates a new change list element to "remember" the model needs to be updated.
	 * 
	 * @param browserID the browser id
	 */
	public void addBrowserVersionView(final int browserID, final String absolutePath) {
		if (browserViews == null) {
			this.init();
		}
		browserViews[browserID - 1].addVersionView(BrowserVersionController.getInstance().createView(browserID, absolutePath));
		this.addToChangeList(new Integer(browserID), ChangeList.SAVE);
	}

	/**
	 * Creates a <code>FileChooser</code> to select an icon for the browser in the dock.
	 * 
	 * @return the complete path to the icon
	 */
	private String loadIcon() {
		final FileChooser chooser = new FileChooser(false);
		chooser.setLocale(Tools.configMng.getLocale());
		chooser.setFileFilter(new String[] { "jpg", "jpeg", "png", "gif" }, Tools.configMng.getStringFor(ConfigManager.STRING_ICONEXT_DESC));
		if (chooser.showOpenDialog(SettingsController.getInstance().getWindow())) {
			return chooser.getCompletePath();
		} else {
			return null;
		}
	}
	
	/**
	 * Creates a <code>FileChooser</code> to select an executable for the browser in the dock.
	 * 
	 * @return the complete path to the executable
	 */
	private String loadExec() {
		final FileChooser chooser = new FileChooser(false);
		chooser.setLocale(Tools.configMng.getLocale());
		chooser.setFileFilter(Controller.getExecutables(), Controller.getExecutablesDesc());
		if (chooser.showOpenDialog(SettingsController.getInstance().getWindow())) {
			return chooser.getCompletePath();
		} else {
			return null;
		}
	}

	/**
	 * Clears the browser view list and the change list.
	 */
	void invalidate() {
		browserViews = null;
		changeList.clear();
	}

	/**
	 * Inits the browser view list if it is null and returns it.
	 * 
	 * @return the browser view list
	 */
	public BrowserView[] getViews() {
		if (browserViews == null) {
			this.init();
		}
		return browserViews;
	}

	/**
	 * Adds the browser id and a change key to the change list.
	 * 
	 * @param id the browser id
	 * @param value the change key
	 */
	public void addToChangeList(final Integer id, final Integer value) {
		changeList.put(id, value);
	}

	/**
	 * Iterates over the change list and invokes the <code>ModelFactory</code>'s methods
	 * to update or delete the browser models.
	 * Clears the browser view list and the change list.
	 */
	public void save() {
		final Iterator<Entry<Integer, Integer>> ibid = changeList.entrySet().iterator();
		while (ibid.hasNext()) {
			final Entry<Integer, Integer> entry = ibid.next();
			final int key = entry.getKey();
			final int value = entry.getValue();
			final BrowserModel bm = new BrowserModel();
			bm.setID(key);
			switch (value) {
			case ChangeList.SAVE: {
				if (browserViews[key - 1].getBrowserName().equalsIgnoreCase(Tools.configMng.getStringFor(ConfigManager.STRING_TEXTFIELD_BROWSERNAME))) {
					bm.setName(Tools.configMng.getStringFor(ConfigManager.STRING_LABEL_NEWBROWSER) + " - " + key);
				} else {
					bm.setName(browserViews[key - 1].getBrowserName());
				}
				bm.setIcon(browserViews[key - 1].getBrowserIcon());
				final Component[] cc = browserViews[key - 1].getBrowserVersions();
				for (final Component bvv : cc) {
					if (bvv instanceof BrowserVersionView && bvv.isEnabled()) {
						final BrowserVersionModel bvm = new BrowserVersionModel();
						bvm.setID(((BrowserVersionView) bvv).getBrowserID());
						bvm.setVersion(((BrowserVersionView) bvv).getBrowserVersion());
						bvm.setExec(((BrowserVersionView) bvv).getBrowserExec());
						bm.add(bvm);
					}
				}
				ModelFactory.updateBrowserModel(bm);
				break;
			}
			case ChangeList.DELETE: {
				ModelFactory.deleteBrowserModel(bm);
			}
			}
		}
		this.invalidate();
	}

	/**
	 * Calls <code>invalidate()</code>.
	 */
	public void abort() {
		this.invalidate();
	}
	
	/**
	 * Forces the browser view with the given id to update.
	 * 
	 * <@param browserID the browser id
	 */
	public void updateGUI(final int browserID) {
		browserViews[browserID - 1].validate();
	}

	/**
	 * Implemented event listener method.
	 * Loads a new icon, deletes the browser or its version,
	 * creates an new version or loads an executable.
	 */
	public void actionPerformed(final ActionEvent e) {
		final int browserID;
		if (e.getSource() instanceof JButton) {
			if (((JButton) e.getSource()).getParent().getParent().getParent() instanceof BrowserView) {
				browserID = ((BrowserView) ((JButton) e.getSource()).getParent().getParent().getParent()).getBrowserID();
				if (e.getActionCommand().equalsIgnoreCase(Tools.configMng.getStringFor(ConfigManager.STRING_BUTTON_BROWSE))) {
					final String iconPath = this.loadIcon();
					if (iconPath != null) {
						browserViews[browserID - 1].setBrowserIconProperties(Tools.configMng.getStringFor(ConfigManager.STRING_LABEL_BROWSERICON), iconPath, Tools.imageMng.createIcon(iconPath, Integer.parseInt(Tools.configMng.getConfiguration(ConfigManager.DOCKLET_ICON_SIZE_MIN)), iconPath), Tools.configMng.getStringFor(ConfigManager.STRING_BUTTON_BROWSE));
						this.addToChangeList(new Integer(browserID), ChangeList.SAVE);
					}
				} else if (e.getActionCommand().equalsIgnoreCase(Tools.configMng.getStringFor(ConfigManager.STRING_BUTTON_BROWSERNEW))) {
					this.addBrowserVersionView(browserID);
					this.updateGUI(browserID);
				} else if (e.getActionCommand().equalsIgnoreCase(Tools.configMng.getStringFor(ConfigManager.STRING_BUTTON_DELETE))) {
					SettingsController.getInstance().setViewEnabled(browserID, false);
					this.addToChangeList(new Integer(browserID), ChangeList.DELETE);
				}
			}
			if (((JButton) e.getSource()).getParent() instanceof BrowserVersionView) {
				final BrowserVersionView bvv = (BrowserVersionView) ((JButton) e.getSource()).getParent();
				if (e.getActionCommand().equalsIgnoreCase(Tools.configMng.getStringFor(ConfigManager.STRING_BUTTON_BROWSE))) {
					String execPath = this.loadExec();
					try {
						if (OSValidator.isWindows()) {
							if (Controller.isOSLink(execPath)) {
								final String fileSource = FileManager.getInstance().parseLink(new File(execPath));
							if (!fileSource.isEmpty()) execPath = fileSource;
							}
						}
						} catch (NullPointerException npe) {
							// an exception does no harm to the business logic
						} catch (Exception ex) {
							// an exception does no harm to the business logic
						}
					if (execPath != null) {
						bvv.setBrowserExec(execPath);
					}
				}
				if (e.getActionCommand().equalsIgnoreCase(Tools.configMng.getStringFor(ConfigManager.STRING_BUTTON_DELETE))) {
					bvv.setEnabled(false);
					for (Component component : bvv.getComponents()) {
						component.setEnabled(false);
					} 
					this.updateGUI(bvv.getBrowserID());
				}
				this.addToChangeList(new Integer(bvv.getBrowserID()), ChangeList.SAVE);
			}
		}
	}

	public void mouseClicked(final MouseEvent e) {
	}

	public void mouseEntered(final MouseEvent e) {
	}

	public void mouseExited(final MouseEvent e) {
	}

	public void mousePressed(final MouseEvent e) {
	}

	public void mouseReleased(final MouseEvent e) {
	}

	/**
	 * Implemented event listener method.
	 * Creates a new change list element whenever the text fields are focused.
	 */
	public void focusGained(final FocusEvent e) {
		if (e.getSource() instanceof JTextField) {
			if (((JTextField) e.getSource()).getParent().getParent().getParent() instanceof BrowserView) {
				final Component c = ((JTextField) e.getSource()).getParent().getParent().getParent();
				this.addToChangeList(new Integer(((BrowserView) c).getBrowserID()), ChangeList.SAVE);
			}
			if (((JTextField) e.getSource()).getParent() instanceof BrowserVersionView) {
				final Component c = ((JTextField) e.getSource()).getParent();
				this.addToChangeList(new Integer(((BrowserVersionView) c).getBrowserID()), ChangeList.SAVE);
			}
		}
	}
	
	public void focusLost(final FocusEvent e) {
	}
}